import locale
import math
import os
import re
import subprocess
import time
import traceback

import func_timeout
import pythoncom
import wmi
from PySide2 import QtCore
from PySide2.QtCore import QThread

from . import network
from .file_utils import empty_folder, decompress_file
from .my_exceptions import MyException


class QTask(QThread):
    step_sgl = QtCore.Signal(str)
    console_sgl = QtCore.Signal(str)
    rsp_sgl = QtCore.Signal(object)

    resource_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resource')
    upgrade_tool = os.path.join(resource_dir, 'upgrade_tool.exe')
    autotest_7z = os.path.join(resource_dir, 'autotest_env.7z')
    autotest_env = r'D:\blue_caches\autotest_env\Scripts\pytest.exe'

    upgrade_files = [
        'MiniLoaderAll.bin',
        'parameter.txt',
        'uboot.img',
        'resource.img',
        'boot_linux.img',
        'ramdisk.img',
        'system.img',
        'vendor.img',
        # 'sys_prod.img',
        # 'chip_prod.img',
        'updater.img',
        # 'chip_ckm.img',
        'userdata.img',
    ]

    def send(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
        self.wait()
        self.start()

    def run(self):
        try:
            self.step_sgl.emit('开始')
            func = getattr(self, self.args[0])
            func(*self.args[1:], **self.kwargs)
            self.step_sgl.emit('结束')
        except:
            self.step_sgl.emit(traceback.format_exc())
            self.step_sgl.emit('异常')

    def list_device(self):
        try:
            out_put = self._execute_upgrade_tool_cmd('LD')
            rockusb = []
            for d in out_put.splitlines():
                if d.startswith('DevNo'):
                    keyword = re.search(r'Vid=0x(?P<vid>\S+),Pid=0x(?P<pid>\S+),LocationID=(?P<locationid>\d+)\tMode=(?P<mode>\S+)', d)
                    vid = keyword.group('vid').upper()
                    pid = keyword.group('pid').upper()
                    locationid = keyword.group('locationid')
                    mode = keyword.group('mode')
                    rockusb.append({
                        'device': d.replace('\t', ','),
                        'vid_pid': 'VID_{}&PID_{}'.format(vid, pid),
                        'locationid': locationid,
                        'mode': mode,
                    })
            return rockusb
        except:
            return []

    def get_device_sn(self, device):
        sn = ''
        pythoncom.CoInitialize()
        c = wmi.WMI()
        for usb in c.Win32_PnPEntity():
            usb_name = usb.Name
            if usb_name and 'HDC Device' in usb_name and device.get('vid_pid') in usb.DeviceID:
                sn = usb.DeviceID.split('\\')[-1].lower()
                break
        pythoncom.CoUninitialize()
        return sn

    def upgrade_by_pr(self, device, pr, img_save_path, use_8g_uboot):
        issue = self._get_issue_by_pr(pr)
        if not issue:
            return
        self.upgrade_by_issue(device, issue, img_save_path, use_8g_uboot)

    def upgrade_by_issue(self, device, issue, img_save_path, use_8g_uboot):
        img_download_link = self._get_img_link_by_issue(issue)
        if not img_download_link:
            return
        self.upgrade_by_img_link(device, img_download_link, img_save_path, use_8g_uboot)

    def upgrade_by_img_link(self, device, img_link, img_save_path, use_8g_uboot):
        dayu200_img = self._download_img_by_img_link(img_link, img_save_path)
        self._upgrade_rk3568(device, dayu200_img, use_8g_uboot)

    def upgrade_by_local_img(self, device, local_img, use_8g_uboot):
        self._upgrade_rk3568(device, local_img, use_8g_uboot)

    def dayu200_dichotomy(self, device, img_download_link, img_save_path, use_8g_uboot):
        dayu200_img = self._download_img_by_img_link(img_download_link, img_save_path)
        self._upgrade_rk3568(device, dayu200_img, use_8g_uboot)

    def sdk_dichotomy(self, img_download_link, img_save_path, ide_config_path):
        sdk_pkg = self._download_img_by_img_link(img_download_link, img_save_path)
        self._decompress_sdk(sdk_pkg, ide_config_path)

    def _get_issue_by_pr(self, pr):
        self.step_sgl.emit('>>> 获取pr对应的issue链接')
        if not pr:
            return
        pulls = re.findall(r".*/(.*/pulls/\d+)", pr)[0]
        tag = network.request_tag_build(request_body={"searchName": pulls})
        if not tag['data']['tagList']:
            self.step_sgl.emit('未获取到对应的issue链接，请确认{}已经合入，如果未合入可以尝试手动输入该pr对应的issue链接或者直接输入镜像的链接'.format(pr))
            return
        issue = tag['data']['tagList'][0]['issue']
        self.step_sgl.emit('issue链接为：{}'.format(issue))
        return issue

    def _get_img_link_by_issue(self, issue):
        self.step_sgl.emit('>>> 获取issue对应的镜像下载链接')
        if not issue:
            return
        last_comment = network.get_last_build_img(issue=issue)
        if not last_comment:
            self.step_sgl.emit('未获取到{}构建的镜像'.format(issue))
            return
        img_download_link = last_comment['versionUrl']
        self.step_sgl.emit('镜像下载链接为：{}'.format(img_download_link))
        return img_download_link

    def _download_img_by_img_link(self, img_link, save_path):
        """
        通过package链接下载镜像
        :param img_link:http://download.ci.openharmony.cn/Artifacts/dayu200/20240105-1-00007/version/Artifacts-dayu200-20240105-1-00007-version-dayu200.tar.gz
        :param save_path:文件保存位置
        :return:镜像保存地址
        """
        img_name = img_link.split('/')[-1]
        save_file = os.path.join(save_path, img_name)
        self.step_sgl.emit('>>> 下载文件')
        self.step_sgl.emit('文件链接：{}'.format(img_link))
        package_size = network.get_img_size(img_link)
        self.step_sgl.emit('文件大小：{}M'.format(round(package_size / 1024 / 1024, 1)))

        if os.path.exists(save_file):
            first_size = os.path.getsize(save_file)
        else:
            first_size = 0
        if first_size >= package_size:
            self.step_sgl.emit('本地已有目标文件存在，不需要重新下载')
            self.step_sgl.emit('文件保存位置：{}'.format(save_file))
            return save_file

        # 断点续传
        start_time = time.time()
        download_rsp = network.download_img(img_link, first_size, package_size)
        with open(save_file, 'ab') as f:
            # 每次写1M
            t1 = time.time()
            down_size = first_size
            for chunk in download_rsp.iter_content(chunk_size=1024 * 1024):
                f.write(chunk)
                try:
                    t2 = time.time()
                    speed = round((len(chunk) / 1024 / 1024) / (t2 - t1), 2)
                    self.step_sgl.emit('下载中：[ {}M/{}M ] {}M/s'.format(round(down_size / 1024 / 1024, 1), round(package_size / 1024 / 1024, 1), speed))
                except:
                    pass
                down_size += len(chunk)
                t1 = t2
        end_time = time.time()
        self.step_sgl.emit('下载完成，耗时：{}s'.format(round(end_time - start_time, 1)))
        self.step_sgl.emit('文件保存位置：{}'.format(save_file))
        return save_file

    def _decompress_dayu200_img(self, tar_file: str):
        """
        解压tar.gz包
        :param tar_file:tar.gz包的路径
        :return:解压后的包的路径
        """
        self.step_sgl.emit('>>> 解压镜像')
        if not os.path.isfile(tar_file) or not tar_file.endswith('tar.gz'):
            raise MyException('没找到镜像{}，或者镜像格式不为tar.gz'.format(tar_file))
        tar_file = '\\\\?\\' + tar_file
        extract_path = tar_file[:-7]
        extracted = []
        for img in self.upgrade_files[:3]:
            extracted.append(os.path.exists(os.path.join(extract_path, img)))

        if all(extracted):
            self.step_sgl.emit('本地已有解压后的镜像，不需要重新解压')
        else:
            self.step_sgl.emit('解压镜像...')
            decompress_file(tar_file, extract_path)
        self.step_sgl.emit('解压后路径：{}'.format(extract_path))
        return extract_path

    def _decompress_sdk(self, sdk_file_dir, ide_path):
        """
        解压sdk到ide目录
        :param sdk_file_dir:
        :param ide_path:
        :return:
        """
        self.step_sgl.emit('>>> 开始安装')
        if not sdk_file_dir or not os.path.isfile(sdk_file_dir):
            raise MyException('sdk 压缩包{}不存在'.format(sdk_file_dir))
        if not ide_path or not os.path.exists(ide_path):
            raise MyException('IDE配置路径未填写或不存在，请先在设置中填写IDE路径'.format(sdk_file_dir))
        self.step_sgl.emit('删除IDE配置路径{}下的所有文件'.format(ide_path))
        empty_folder(ide_path)
        if not os.listdir(ide_path):
            self.step_sgl.emit('删除成功')
        self.step_sgl.emit('正在解压sdk包{}到IDE配置路径{}下...'.format(sdk_file_dir, ide_path))
        decompress_file(sdk_file_dir, ide_path)
        self.step_sgl.emit('解压成功')
        target_path = os.path.join(ide_path, 'windows')
        for zip_file in os.listdir(target_path):
            if zip_file.endswith('.zip'):
                zip_path = os.path.join(target_path, zip_file)
                self.step_sgl.emit('正在解压{}到IDE配置路径{}下...'.format(zip_path, ide_path))
                decompress_file(zip_path, ide_path)
                self.step_sgl.emit('解压成功')
        self.step_sgl.emit('>>> 安装成功')

    def run_testcases(self, device, testcase_path, selected_cases):
        org_dir = os.getcwd()
        try:
            self.console_sgl.emit('>>> 开始测试')
            if not os.path.exists(self.autotest_env):
                self.console_sgl.emit('>>> 解压测试环境')
                decompress_file(self.autotest_7z, r'D:\blue_caches')
            self.console_sgl.emit('获取设备序列号...')
            sn = self.get_device_sn(device)
            os.chdir(testcase_path)
            run_params = '"{}"  -vs --sn={} --html=./report/{}_report.html --self-contained-html --capture=sys {}'.format(self.autotest_env, sn, sn, ' '.join(selected_cases))
            # self.console_sgl.emit(run_params)
            # system_encoding = locale.getpreferredencoding().lower()
            # if system_encoding == 'cp936':
            #     encoding = 'gbk'
            # else:
            #     encoding = 'utf-8'
            # p = subprocess.Popen(run_params, shell=True, stdout=subprocess.PIPE, encoding=encoding, errors='ignore')
            p = subprocess.Popen(run_params, shell=True, stdout=subprocess.PIPE, encoding='utf-8', errors='ignore')

            while True:
                output = p.stdout.readline()
                if not output and p.poll() is not None:
                    break
                self.console_sgl.emit(output)
        except Exception as e:
            self.console_sgl.emit(traceback.format_exc())
        finally:
            os.chdir(org_dir)
            self.console_sgl.emit('>>> 结束测试')

    def _upgrade_rk3568(self, device, compressed_img, use_8g_uboot=False):
        if not compressed_img or not compressed_img.endswith('tar.gz') or not os.path.exists(compressed_img):
            raise MyException('镜像{}不存在或者格式不对'.format(compressed_img))
        extract_folder = self._decompress_dayu200_img(compressed_img)

        location_id = self._get_loader_id(device)
        if not location_id:
            raise MyException('进入Loader模式失败，请查看控制台信息！')
        self.step_sgl.emit('>>> 开始刷机')

        for img in self.upgrade_files:
            self.step_sgl.emit('开始烧录{}...'.format(img))
            img_path = os.path.join(extract_folder, img)
            if img == 'MiniLoaderAll.bin':
                if not os.path.isfile(img_path):
                    raise FileNotFoundError(img_path)
                cmd = '-s {} UL "{}" -noreset'.format(location_id, img_path)
                rst = self._execute_upgrade_tool_cmd(cmd)
                if 'Upgrade loader ok' not in rst:
                    raise MyException('{} 烧录失败，请查看控制台信息！'.format(img))
                self.step_sgl.emit('{} 烧录成功！'.format(img))
            elif img == 'parameter.txt':
                if not os.path.isfile(img_path):
                    raise FileNotFoundError(img_path)
                cmd = '-s {} DI -p "{}"'.format(location_id, img_path)
                rst = self._execute_upgrade_tool_cmd(cmd)
                if 'Write gpt ok' not in rst:
                    raise MyException('{} 烧录失败，请查看控制台信息！'.format(img))
                self.step_sgl.emit('{} 烧录成功！'.format(img))
            elif img == 'uboot.img':
                if use_8g_uboot:
                    img_path = os.path.join(self.resource_dir, 'uboot.img')
                if not os.path.isfile(img_path):
                    raise FileNotFoundError(img_path)
                cmd = '-s {} DI -uboot "{}" "{}"'.format(location_id, img_path, os.path.join(extract_folder, 'parameter.txt'))
                rst = self._execute_upgrade_tool_cmd(cmd)
                if 'Download image ok' not in rst:
                    raise MyException('{} 烧录失败，请查看控制台信息！'.format(img))
                self.step_sgl.emit('{} 烧录成功！'.format(img))
            else:
                if not os.path.isfile(img_path):
                    self.step_sgl.emit('{} 文件缺失！'.format(img))
                    continue
                key = img.split('.')[0]
                if key in ['sys_prod', 'chip_prod']:
                    key = key.replace('_', '-')
                cmd = '-s {} DI -{} "{}"'.format(location_id, key, img_path)
                rst = self._execute_upgrade_tool_cmd(cmd)
                if 'Download image ok' not in rst:
                    raise MyException('{} 烧录失败，请查看控制台信息！'.format(img))
                self.step_sgl.emit('{} 烧录成功！'.format(img))

        self.step_sgl.emit('镜像文件烧录完成')
        time.sleep(1)
        self.step_sgl.emit('重启设备')
        self._execute_upgrade_tool_cmd('-s {} RD'.format(location_id))
        self.step_sgl.emit('>>> 刷机完成')

    def _execute_cmd(self, cmd_str, timeout=None):
        self.console_sgl.emit('{}>{}'.format(os.getcwd(), cmd_str))
        rst = subprocess.run(cmd_str, capture_output=True, shell=True, encoding='utf-8', timeout=timeout)
        out_put = rst.stdout or rst.stderr
        self.console_sgl.emit(out_put)
        return out_put

    def _execute_upgrade_tool_cmd(self, cmd_str):
        upgrade_cmd = f'"{self.upgrade_tool}" {cmd_str}'
        return self._execute_cmd(upgrade_cmd)

    def _get_loader_id(self, device):
        self.step_sgl.emit('>>> 进入Loader模式')
        mode = device.get('mode')
        if mode == 'Loader':
            self.step_sgl.emit('设备已处于Loader模式')
            location_id = device.get('locationid')
            return location_id
        else:
            sn = self.get_device_sn(device)
            self._reboot_loader(sn)
            # 进loader之后再查一次
            after_reboot = self.list_device()
            if len(after_reboot) == 1 and after_reboot[0].get('mode') == 'Loader':
                return after_reboot[0].get('locationid')

    @func_timeout.func_set_timeout(20)
    def _reboot_loader(self, sn):
        self._execute_cmd(f'hdc -t {sn} shell reboot loader')
        time.sleep(5)

    def get_ci_projects(self):
        """
        获取每日构建和tag构建的所有项目和分支信息
        :return:
        """
        ci_projects = network.ci_projects()
        self.rsp_sgl.emit(ci_projects)

    def get_build_components(self, request_data):
        """
        获取流水线形态
        :param request_data:
        :return:
        """
        # 滚动构建
        rsp_data = network.request_build_components(request_data)
        total_size = rsp_data['data']['builds']['totalSize']
        data_list = rsp_data['data']['builds']['dataList']
        components = []
        for data in data_list:
            component_name = data['component']
            if component_name not in components:
                components.append(component_name)

        if total_size > 50:
            pages = math.ceil(total_size / 50)
            for page_num in range(2, pages + 1):
                request_data.update({'pageNum': page_num})
                rsp_data = network.request_build_components(request_data)
                data_list = rsp_data['data']['builds']['dataList']
                for data in data_list:
                    component_name = data['component']
                    if component_name not in components:
                        components.append(component_name)

        # 每日构建
        request_data.update({"type": "0", "pageNum": 1})
        rsp_data = network.request_build_components(request_data)
        total_size = rsp_data['data']['builds']['totalSize']
        data_list = rsp_data['data']['builds']['dataList']
        for data in data_list:
            component_name = data['component']
            if component_name not in components:
                components.append(component_name)

        if total_size > 50:
            pages = math.ceil(total_size / 50)
            for page_num in range(2, pages + 1):
                request_data.update({'pageNum': page_num})
                rsp_data = network.request_build_components(request_data)
                data_list = rsp_data['data']['builds']['dataList']
                for data in data_list:
                    component_name = data['component']
                    if component_name not in components:
                        components.append(component_name)

        self.rsp_sgl.emit(components)

    def fetch_manifest(self, project, branch):
        tag_projects = network.get_tag_projects()
        manifest_list = tag_projects.get(project, {}).get(branch)
        self.rsp_sgl.emit(manifest_list)

    def daily_build_list(self, request_body):
        """
        获取日构建的版本镜像
        :param request_body:
        :return:
        """
        domain = 'http://cidownload.openharmony.cn/'
        component = request_body['component'].lower()
        rsp_data = network.request_version_build(request_body)
        total = rsp_data['data']['count']

        all_version_list = []
        for version in rsp_data['data']['list']['dataList']:
            if version['buildSuccess'] == 'true':
                print(version)
                bst = version['buildStartTime']
                build_start_time = '{}-{}-{} {}:{}:{}'.format(bst[:4], bst[4:6], bst[6:8], bst[8:10], bst[10:12], bst[12:])
                if 'ohos-sdk' in component:
                    img_obs_path = version['obsPath']
                    if img_obs_path.startswith('version'):
                        img_obs_path = domain + img_obs_path
                else:
                    img_obs_path = version['imgObsPath']
                all_version_list.append({'build_start_time': build_start_time, 'img_obs_path': img_obs_path})

        if total > 50:
            pages = math.ceil(total / 50)
            for page_num in range(2, pages + 1):
                request_body.update({'pageNum': page_num})
                # rsp_data = self.request_tag_build(start_date, end_date, manifest, page_num=page_num)
                rsp_data = network.request_version_build(request_body)
                for version in rsp_data['data']['list']['dataList']:
                    if version['buildSuccess'] == 'true':
                        bst = version['buildStartTime']
                        build_start_time = '{}-{}-{} {}:{}:{}'.format(bst[:4], bst[4:6], bst[6:8], bst[8:10], bst[10:12], bst[12:])
                        if 'ohos-sdk' in component:
                            img_obs_path = version['obsPath']
                            if img_obs_path.startswith('version'):
                                img_obs_path = domain + img_obs_path
                        else:
                            img_obs_path = version['imgObsPath']
                        all_version_list.append({'build_start_time': build_start_time, 'img_obs_path': img_obs_path})

        request_body.update({'type': 1, 'pageNum': 1})
        daily_build = network.request_version_build(request_body)
        for version in daily_build['data']['list']['dataList']:
            if version['buildSuccess'] == 'true':
                bst = version['buildStartTime']
                build_start_time = '{}-{}-{} {}:{}:{}'.format(bst[:4], bst[4:6], bst[6:8], bst[8:10], bst[10:12], bst[12:])
                if 'ohos-sdk' in component:
                    img_obs_path = version['obsPath']
                    if img_obs_path.startswith('version'):
                        img_obs_path = domain + img_obs_path
                else:
                    img_obs_path = version['imgObsPath']
                all_version_list.append({'build_start_time': build_start_time, 'img_obs_path': img_obs_path})
        all_version_list.sort(key=lambda ver: ver['build_start_time'], reverse=True)
        self.rsp_sgl.emit(all_version_list)

    def fetch_trigger_build(self, request_params, build_target='dayu200'):
        self.rsp_sgl.emit('开始')
        try:
            build_events = network.get_build_events(request_params).get('data', {})
            total_trigger = build_events.get('total', 0)
            download_url = build_events.get('downloadUrl', '')
            events = build_events.get('events')
            self._get_one_of_events(events, download_url, build_target)
            if total_trigger > 50:
                pages = math.ceil(total_trigger / 50)
                for page_num in range(2, pages + 1):
                    request_params.update({'pageNum': page_num})
                    build_events = network.get_build_events(request_params).get('data', {})
                    events = build_events.get('events')
                    self._get_one_of_events(events, download_url, build_target)
        finally:
            self.rsp_sgl.emit('结束')

    def _get_one_of_events(self, events, download_url, build_target='dayu200'):
        for event in events:
            owner = event['prMsg'][0]['owner']
            pr_url = event['prMsg'][0]['url']
            ts = event['timestamp']
            timestamp = '{}-{}-{} {}:{}:{}'.format(ts[:4], ts[4:6], ts[6:8], ts[8:10], ts[10:12], ts[12:])
            builds = event['builds']
            for build in builds:
                if build_target == build.get('buildTarget') and build['debug']['result'] == 'success':
                    artifacts = build['debug']['Artifacts']
                    file_data = network.get_build_files(artifacts)
                    if file_data['data']['version']:
                        img = file_data['data']['version']
                        for attr, file_dict in img.items():
                            url = file_dict.get('url')
                            data = {
                                'owner': owner,
                                'timestamp': timestamp,
                                'pr_url': pr_url,
                                'img_link': download_url + url,
                            }
                            self.rsp_sgl.emit(data)
                            return

    def get_tag_build(self, request_body, component, start_time, end_time):
        """
        获取区间内的pr tag点
        :param request_body:
        :param component:
        :param start_time:
        :param end_time:
        :return:
        """
        self.rsp_sgl.emit('开始')
        try:
            rsp_data = network.request_tag_build(request_body)
            total = rsp_data['data']['total']
            for tag in rsp_data['data']['tagList']:
                satisfy_tag = network.filter_tag(component, start_time, end_time, tag)
                if satisfy_tag:
                    self.rsp_sgl.emit(satisfy_tag)
                # self.filter_tag_info(start_time, end_time, tag)
            if total > 50:
                pages = math.ceil(total / 50)
                for page_num in range(2, pages + 1):
                    request_body.update({"pageNum": page_num})
                    rsp_data = network.request_tag_build(request_body)
                    for tag in rsp_data['data']['tagList']:
                        satisfy_tag = network.filter_tag(component, start_time, end_time, tag)
                        if satisfy_tag:
                            self.rsp_sgl.emit(satisfy_tag)
        finally:
            self.rsp_sgl.emit('结束')
